// Copyright (c) 2010 SuccessFactors, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
//     * Redistributions of source code must retain the above
//       copyright notice, this list of conditions and the following
//       disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials
//       provided with the distribution.
//
//     * Neither the name of the SuccessFactors, Inc. nor the names of
//       its contributors may be used to endorse or promote products
//       derived from this software without specific prior written
//       permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.

package org.owasp.jxt.webinf;

import java.io.IOException;
import java.io.InputStream;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

/**
 * TLDReader
 *
 * @author Jeffrey Ichnowski
 * @version $Revision: 8 $
 */
public final class TLDReader {
    static final SAXParserFactory SAX_FACTORY = SAXParserFactory.newInstance();
    static {
        SAX_FACTORY.setNamespaceAware(true);
        SAX_FACTORY.setValidating(true);
    }

    static final PublicId TLD_1_1_PUBLICID = new PublicId(
        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN",
        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd");

    static final String TLD_1_1_XMLNS =
        "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd";


    static final PublicId TLD_1_2_PUBLICID = new PublicId(
        "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN",
        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd");

    static final String TLD_1_2_XMLNS =
        "http://java.sun.com/JSP/TagLibraryDescriptor";

    SAXParser _parser;
    XMLReader _reader;
    TagLibraryInfoImpl _tld = new TagLibraryInfoImpl("prefix", "uri");

    DefaultHandler _baseHandler = new DefaultHandler() {
        @Override
        public InputSource resolveEntity(String publicId, String systemId)
            throws IOException, SAXException
        {
            PublicId dtd;

            if (TLD_1_1_PUBLICID.matches(publicId, systemId)) {
                dtd = TLD_1_1_PUBLICID;
                _reader.setContentHandler(createContentHandler1dot1());
            } else if (TLD_1_2_PUBLICID.matches(publicId, systemId)) {
                dtd = TLD_1_2_PUBLICID;
                _reader.setContentHandler(createContentHandler1dot2());
            } else {
                throw new SAXException("Unrecognized DTD name: "+publicId+", "+systemId);
            }

            InputStream in = dtd.resolveLocal(getClass());

            if (in == null) {
                throw new SAXException("Could not locate "+systemId);
            }

            return new InputSource(in);
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attrs)
            throws SAXException
        {
            throw new SAXException("Document does not specify a DTD");
        }
    };

    static enum TLDParseState {
        UNKNOWN, TOP, TAGLIB, TAG, TLIBVERSION, JSPVERSION, SHORTNAME,
        URI, INFO, TAG_NAME, TAG_TAGCLASS, TAG_TEICLASS, TAG_BODYCONTENT,
        TAG_INFO, ATTRIBUTE, ATTR_NAME, ATTR_REQ, ATTR_RTEXPR;
    }

    ContentHandler createContentHandler1dot1() {
        return new XmlHandler<TLDParseState>(TLDParseState.TOP) {
            TagDefinition _tag;
            AttributeDefinition _attr;

            @Override
            public TLDParseState pushElement(String uri, String localName, String qName, Attributes attrs)
                throws SAXException
            {
                switch (_state) {
                case TOP:
                    if ("taglib".equals(localName)) {
                        return TLDParseState.TAGLIB;
                    } else {
                        throw new SAXException("Expected <taglib>, found <"+localName+">");
                    }
                case TAGLIB:
                    if ("tag".equals(localName)) {
                        _tag = new TagDefinition();
                        return TLDParseState.TAG;
                    } else if ("tlibversion".equals(localName)) {
                        return TLDParseState.TLIBVERSION;
                    } else if ("jspversion".equals(localName)) {
                        return TLDParseState.JSPVERSION;
                    } else if ("shortname".equals(localName)) {
                        return TLDParseState.SHORTNAME;
                    } else if ("uri".equals(localName)) {
                        return TLDParseState.URI;
                    } else if ("info".equals(localName)) {
                        return TLDParseState.INFO;
                    } else {
                        return TLDParseState.UNKNOWN;
                    }
                case TAG:
                    if ("name".equals(localName)) {
                        return TLDParseState.TAG_NAME;
                    } else if ("tagclass".equals(localName)) {
                        return TLDParseState.TAG_TAGCLASS;
                    } else if ("teiclass".equals(localName)) {
                        return TLDParseState.TAG_TEICLASS;
                    } else if ("bodycontent".equals(localName)) {
                        return TLDParseState.TAG_BODYCONTENT;
                    } else if ("info".equals(localName)) {
                        return TLDParseState.TAG_INFO;
                    } else if ("attribute".equals(localName)) {
                        _attr = new AttributeDefinition();
                        return TLDParseState.ATTRIBUTE;
                    } else {
                        return TLDParseState.UNKNOWN;
                    }
                case ATTRIBUTE:
                    if ("name".equals(localName)) {
                        return TLDParseState.ATTR_NAME;
                    } else if ("required".equals(localName)) {
                        return TLDParseState.ATTR_REQ;
                    } else if ("rtexprvalue".equals(localName)) {
                        return TLDParseState.ATTR_RTEXPR;
                    } else {
                        return TLDParseState.UNKNOWN;
                    }
                default:
                    throw new InternalError("Invalid state: "+_state);
                }
            }

            @Override
            public void popElement(String uri, String localName, String qName, String content)
                throws SAXException
            {
                switch (_state) {
                case TLIBVERSION:
                    _tld.setTlibVersion(content);
                    break;
                case JSPVERSION:
                    _tld.setJspVersion(content);
                    break;
                case SHORTNAME:
                    _tld.setShortName(content);
                    break;
                case URI:
                    _tld.setURI(content);
                    break;
                case INFO:
                    _tld.setInfoString(content);
                    break;
                case TAG_NAME:
                    _tag.setName(content);
                    break;
                case TAG_TAGCLASS:
                    _tag.setTagClass(content);
                    break;
                case TAG_TEICLASS:
                    _tag.setTeiClass(content);
                    break;
                case TAG_BODYCONTENT:
                    _tag.setBodyContent(BodyContentType.valueOf(content));
                    break;
                case TAG_INFO:
                    _tag.setInfoString(content);
                    break;
                case ATTR_NAME:
                    _attr.setName(content);
                    break;
                case ATTR_REQ:
                    _attr.setRequired(booleanFor(content));
                    break;
                case ATTR_RTEXPR:
                    _attr.setRtExprValue(booleanFor(content));
                    break;

                case TAG:
                    _tld.addTag(_tag);
                    _tag = null;
                    break;

                case ATTRIBUTE:
                    _tag.addAttribute(_attr);
                    _attr = null;
                    break;

                default:
                    break;
                }
            }
        };
    }

    ContentHandler createContentHandler1dot2() throws SAXException {
        throw new SAXException("TLD version 1.2 is not implemented");
//         return new DefaultHandler() {
//             @Override
//             public void startElement(String uri, String localName, String qName, Attributes attrs)
//                 throws SAXException
//             {
//                 System.out.println("1.2 "+uri+" "+localName);
//             }
//         };
    }

    private TLDReader() throws ParserConfigurationException, SAXException {
        _parser = SAX_FACTORY.newSAXParser();
        _reader = _parser.getXMLReader();
        _reader.setEntityResolver(_baseHandler);
        _reader.setErrorHandler(_baseHandler);
        _reader.setDTDHandler(_baseHandler);
        _reader.setContentHandler(_baseHandler);
    }

    public static TagLibraryInfoImpl parse(InputStream in)
        throws ParserConfigurationException, SAXException, IOException
    {
        return new TLDReader().doParse(new InputSource(in));
    }

    private TagLibraryInfoImpl doParse(InputSource in)
        throws SAXException, IOException
    {
        _reader.parse(in);
        return _tld;
    }

//     public static void main(String[] args) throws Exception {
//         System.out.println(parse(new java.io.FileInputStream(args[0])));
//     }
} // TLDReader
